﻿using Xejen.Communication.Configuration;
using Xejen.Communication.Endpoints;
using Xejen.Communication.Logger;
using Xejen.Logger;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;

namespace Xejen.Communication
{
    /// <inheritdoc cref="ICommunicator{TCommunicationConfig, THeartbeatConfig}"/>
    /// <typeparam name="TCommunicationConfig"><inheritdoc cref="CommunicationConfig{THeartbeatConfig}" path="/summary"/></typeparam>
    /// <typeparam name="THeartbeatConfig"><inheritdoc cref="HeartbeatConfig" path="/summary"/></typeparam>
    /// <typeparam name="TCommunicationAddress">通讯地址</typeparam>
    public abstract class Communicator<TCommunicationConfig, THeartbeatConfig, TCommunicationAddress> : ICommunicator<TCommunicationConfig, THeartbeatConfig>
        where TCommunicationConfig : CommunicationConfig<THeartbeatConfig>, new()
        where THeartbeatConfig : HeartbeatConfig, new()
    {
        /// <summary>
        /// 当通讯器停用时的消息提示
        /// </summary>
        private readonly string _alarmWhenCommunicatorDisabled = $"通讯器未启用，如若需要启用，请设置：{nameof(Enable)}属性为true.";
        /// <summary>
        /// 当通讯器停用时的消息提示
        /// </summary>
        protected string AlarmWhenCommunicatorDisabled => _alarmWhenCommunicatorDisabled;

        /// <inheritdoc/>
        protected virtual ILogger Logger { get; set; }
        /// <inheritdoc/>
        protected bool Enable => Config.Enable;

        /// <summary>
        /// 序列化参数
        /// </summary>
        protected virtual JsonSerializerOptions JsonSerializerOptions { get; }

        /// <inheritdoc/>
        public TCommunicationConfig Config { get; set; }

        private IHeartbeat<THeartbeatConfig> _heartbeat;
        /// <inheritdoc/>
        public virtual IHeartbeat<THeartbeatConfig> Heartbeat
        {
            get
            {
                if (_heartbeat == null)
                {
                    _heartbeat = new CommunicationHeartbeat<THeartbeatConfig>(Config.HeartbeatConfig);
                }

                return _heartbeat;
            }
        }

        /// <inheritdoc/>
        protected IDatabaseLogger DatabaseLogger { get; set; }

        /// <inheritdoc cref="Communicator{TConfig, THeartbeatConfig, TAddress}"/>
        /// <param name="config"><inheritdoc cref="Config" path="/summary"/></param>
        /// <param name="loggerManager"><inheritdoc cref="ILoggerManager" path="/summary"/></param>
        protected Communicator(TCommunicationConfig config, ILoggerManager loggerManager)
        {
            Check.NotNull(config, nameof(config));
            Check.NotNull(loggerManager, nameof(loggerManager));

            Config = config;
            Logger = loggerManager.CreateLogger<Communicator<TCommunicationConfig, THeartbeatConfig, TCommunicationAddress>>();

            Heartbeat?.StartAsync(CancellationToken.None);
        }

        /// <inheritdoc/>
        public void SetConfig(TCommunicationConfig config)
        {
            Config = config;
        }

        /// <inheritdoc/>
        public void RegisterDatabaseLogger(IDatabaseLogger databaseLogger)
        {
            Check.NotNull(databaseLogger, nameof(databaseLogger));

            DatabaseLogger = databaseLogger;
        }

        /// <summary>
        /// 记录任务调用日志
        /// </summary>
        /// <typeparam name="TResponse">响应结果</typeparam>
        /// <param name="action">待被调用的任务</param>
        /// <param name="address">通讯地址包装</param>
        /// <param name="input">输入参数</param>
        /// <returns></returns>
        protected virtual TResponse LogInvoke<TResponse>(Func<TResponse> action, TCommunicationAddress address, object input = null) where TResponse : class, IResult, new()
        {
            return InnerLogInvokeAsync(() => Task.FromResult(action()), address, null, null, input).GetAwaiter().GetResult();
        }

        /// <summary>
        /// 记录任务调用日志
        /// </summary>
        /// <typeparam name="TResponse">响应结果</typeparam>
        /// <param name="task">待被调用的任务</param>
        /// <param name="address">通讯地址包装</param>
        /// <param name="input">输入参数</param>
        /// <param name="timeout">超时时间</param>
        /// <param name="cancellationToken">线程取消令牌</param>
        /// <returns></returns>
        protected virtual async Task<TResponse> LogInvokeAsync<TResponse>(Task<TResponse> task, TCommunicationAddress address, int timeout, object input = null, CancellationToken cancellationToken = default) where TResponse : class, IResult, new()
        {
            return await InnerLogInvokeAsync(() => task, address, task, timeout, input, cancellationToken);
        }

        private async Task<TResponse> InnerLogInvokeAsync<TResponse>(
            Func<Task<TResponse>> taskFunc,
            TCommunicationAddress address,
            Task task,
            int? timeout,
            object input,
            CancellationToken cancellationToken = default)
            where TResponse : class, IResult, new()
        {
            if (!Enable)
            {
                Debug.WriteLine(_alarmWhenCommunicatorDisabled);
                return new TResponse { Code = Result.SuccessCode };
            }

            if (Config.LogVerbosity >= LogVerbosity.Normal)
            {
                Logger.LogInformation($"发起请求：{{@value}}", new { TaskId = task?.Id, Address = address, Model = input.ToJson(Config.JsonSerializerOptions) });
            }

            TResponse response = null;
            Stopwatch stopwatch = Stopwatch.StartNew();
            try
            {
                response = await taskFunc().ConfigureAwait(false);
                if (!response.IsSuccess)
                {
                    LogError(response.Exception, nameof(InnerLogInvokeAsync), new { Address = address, Timeout = timeout, Input = input });
                }
            }
            catch (Exception exp)
            {
                Debug.WriteLine($"方法 [{nameof(InnerLogInvokeAsync)}] 执行异常:{exp}");
                LogError(exp, nameof(InnerLogInvokeAsync), new { Address = address, Timeout = timeout, Input = input });
                response = new TResponse { Code = 201, Exception = exp };
            }
            finally
            {
                stopwatch.Stop();
                DateTime now = StandardTime.Now;
                var executionInfo = new ExecutionInfo
                {
                    Duration = stopwatch.Elapsed,
                    StartTime = now - stopwatch.Elapsed,
                    EndTime = now
                };

                if (Config.LogVerbosity >= LogVerbosity.Normal)
                {
                    string alias = response.IsSuccess ? "成功" : "失败";
                    Logger.LogInformation($"请求结束({alias})：{{@value}}", new { TaskId = task?.Id, Address = address, Timeout = timeout, Response = response.GetSerializableObject() });
                }

                try
                {
                    if (!cancellationToken.IsCancellationRequested)
                    {
                        DatabaseLogger?.SaveAsync<TCommunicationConfig, THeartbeatConfig, TCommunicationAddress, TResponse>(new LogData<TCommunicationAddress, TResponse>
                        {
                            Address = address,
                            Input = input,
                            Response = response,
                            Task = task,
                            ExecutionInfo = executionInfo
                        }, Config, cancellationToken);
                    }
                }
                catch (System.Exception exp)
                {
                    Debug.WriteLine($"方法 [{nameof(InnerLogInvokeAsync)}] 写数据库日志时异常:{exp}");
                    LogError(exp, nameof(LogInvokeAsync), new { Address = address, Timeout = timeout, Input = input });
                }
            }

            return response;
        }

        private void LogError(Exception exp, string methodName, object arg)
        {
            Logger.LogError(exp, $"[{methodName}]\r\n请求参数：\r\n{{@value}}\r\n错误信息：\r\n", arg);
        }

        /// <summary>
        /// 取超时时间，若输入的时间 小于等于0，则取配置时间
        /// </summary>
        /// <param name="timeout"></param>
        /// <returns></returns>
        protected virtual int GetTimeout(int timeout)
        {
            if (timeout <= 0)
            {
                timeout = Config.Timeout;
            }

            return timeout;
        }
    }
}
